Skip to content

[codex] Encrypt persisted storage values#585

Merged
swolfand merged 1 commit intomainfrom
codex/encrypt-local-storage
Apr 9, 2026
Merged

[codex] Encrypt persisted storage values#585
swolfand merged 1 commit intomainfrom
codex/encrypt-local-storage

Conversation

@swolfand
Copy link
Copy Markdown
Collaborator

@swolfand swolfand commented Apr 9, 2026

What changed

  • encrypt StorageHelper values before writing them to disk while keeping SharedPreferences as the persistence backend
  • add a dedicated StorageCipher abstraction with Android Keystore-backed AES/GCM encryption in production
  • migrate legacy plaintext values on read and clear undecryptable entries instead of returning corrupted state
  • add storage regression tests covering encrypted-at-rest writes, plaintext migration, malformed ciphertext cleanup, and existing CRUD/concurrency behavior
  • use a JVM AES/GCM fallback cipher in Robolectric so unit tests still exercise encrypted persistence semantics

Why

Local SDK values were being stored as plaintext strings. The right fix was to harden the single storage seam rather than layering ad hoc protection at call sites.

Impact

  • device tokens, device IDs, and pending native magic-link flow state are now encrypted at rest
  • existing installs migrate stored plaintext values transparently on first successful read
  • malformed or undecryptable encrypted entries are dropped instead of surfacing bad data to callers

Root cause

StorageHelper persisted sensitive values directly through SharedPreferences, so any persisted storage entry was written in plaintext.

Validation

  • ./gradlew :source:api:testDebugUnitTest --tests 'com.clerk.api.storage.StorageHelperTest' --tests 'com.clerk.api.signout.SignOutServiceTest' --tests 'com.clerk.api.sdk.ClerkDeviceTokenUpdateTest' --tests 'com.clerk.api.magiclink.PersistentPendingNativeMagicLinkStoreTest' --tests 'com.clerk.api.network.middleware.outgoing.VersioningUserAgentMiddlewareTest' :source:api:spotlessCheck :source:api:detekt

Encrypt StorageHelper values with an AES-GCM envelope backed by AndroidKeyStore keys in production. Migrate legacy plaintext entries on read, drop undecryptable values, and use a Robolectric-safe JVM cipher so storage tests still exercise encrypted persistence semantics.
@swolfand swolfand marked this pull request as ready for review April 9, 2026 01:18
@swolfand swolfand merged commit 1580bc7 into main Apr 9, 2026
10 checks passed
@swolfand swolfand deleted the codex/encrypt-local-storage branch April 9, 2026 01:18
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2fd54bb3-68f9-4312-a881-1e64bf3a91e0

📥 Commits

Reviewing files that changed from the base of the PR and between e2b5404 and a9f48e2.

📒 Files selected for processing (3)
  • source/api/src/main/kotlin/com/clerk/api/storage/StorageCipher.kt
  • source/api/src/main/kotlin/com/clerk/api/storage/StorageHelper.kt
  • source/api/src/test/java/com/clerk/api/storage/StorageHelperTest.kt

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


📝 Walkthrough

Walkthrough

A new StorageCipher abstraction has been introduced to encrypt and decrypt UTF-8 strings using AES-GCM encryption. The StorageCipherFactory creates cipher instances, preferring an Android Keystore-backed implementation and falling back to a JVM-based AES-GCM implementation for testing environments. The StorageHelper class has been updated to encrypt values before persisting them to SharedPreferences using an encrypted:v1: prefix, with logic to migrate existing plaintext values to encrypted format. Test coverage has been expanded to verify encryption behavior, legacy value migration, and handling of corrupted encrypted data.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant